import tkinter as tk
from tkinter import ttk, filedialog, messagebox, scrolledtext
import threading
import os
import requests
import re
import json
import subprocess
import tempfile
from urllib.parse import urlparse
import pickle

class BilibiliDownloader:
    def __init__(self, root):
        self.root = root
        self.root.title("B站视频下载器")
        self.root.geometry("1000x800")
        self.root.resizable(True, True)
        
        # 设置窗口图标和样式
        self.root.configure(bg='#f0f0f0')
        
        # 下载状态变量
        self.downloading = False
        self.download_thread = None
        
        # 请求头配置
        self.headers = {
            'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36',
            'cookie': 'buvid_fp=a2776ae5e59e89fa65e3fa0c0b9af831; enable_web_push=DISABLE; header_theme_version=CLOSE; rpdid=0zbfAHZmhs|1eGISvrhj|2D|3w1SHyB1; enable_feed_channel=ENABLE; theme-tip-show=SHOWED; theme-avatar-tip-show=SHOWED; buvid4=F3519C62-6C68-46FC-0A1B-ED357B6868B048043-024082318-x6aAbwyB8DiP9T2/6+H+JwFZQ4yy5E/UTRNkD/nwKA0WJYNr9T2aN4casuQODwJM; hit-dyn-v2=1; buvid3=20EF52A9-7CAE-5A5F-F3D4-71492A34886991698infoc; b_nut=1757749991; _uuid=51C24317-262E-B7D8-294F-210E9239CC49692997infoc; bili_ticket=eyJhbGciOiJIUzI1NiIsImtpZCI6InMwMyIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3NTgwMDkxOTIsImlhdCI6MTc1Nzc0OTkzMiwicGx0IjotMX0.Hne74-i9e6PYv0RDUddPm77nkTGCWufKxqB6libLvXo; bili_ticket_expires=1758009132; home_feed_column=5; browser_resolution=2133-1012; theme-switch-show=SHOWED; SESSDATA=0a7f7e76%2C1773371934%2C88dac%2A91CjAdaDQCK2OPLAYPeiNqHu6yktx5IgBjNAY3PlHv67ppif6kDBJ3-QpRqrsLvC1kWl8SVk1ZZmZtZFQyZ0NDNVF1UGVQY0dVZWNmYkdTMzR1T2VEQmVWRU9YMEJQX0diLXlJMnhqZDZ4RXZxejVjSkI5OG1PaDdtNk8wVVBQU2lHSmhta3k4V1hBIIEC; bili_jct=ba88bf16e770eeea1e76188399e670d9; DedeUserID=690636703; DedeUserID__ckMd5=1663d9a16ab939e7; bsource=search_baidu; bmg_af_switch=1; bmg_src_def_domain=i0.hdslb.com; sid=7vi2ploi; _tea_utm_cache_20001731={%22creative_id%22:479486067}; CURRENT_QUALITY=80; bp_t_offset_690636703=1112510140138913792; b_lsid=5CF9EBB3_19948F49AB3; CURRENT_FNVAL=4048',
            'referer': 'https://www.bilibili.com/'
        }
        
        # 创建界面
        self.create_widgets()
        
        # 初始化Cookie
        self.cookie_file = "cookie.dat"
        self.init_cookie()
        
    def create_widgets(self):
        # 主框架
        main_frame = ttk.Frame(self.root, padding="20")
        main_frame.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
        
        # 配置网格权重
        self.root.columnconfigure(0, weight=1)
        self.root.rowconfigure(0, weight=1)
        main_frame.columnconfigure(1, weight=1)
        
        # 标题
        title_label = ttk.Label(main_frame, text="B站视频下载器", font=("Arial", 16, "bold"))
        title_label.grid(row=0, column=0, columnspan=3, pady=(0, 20))
        
        # URL输入区域
        url_frame = ttk.LabelFrame(main_frame, text="视频链接", padding="10")
        url_frame.grid(row=1, column=0, columnspan=3, sticky=(tk.W, tk.E), pady=(0, 10))
        url_frame.columnconfigure(0, weight=1)
        
        self.url_var = tk.StringVar()
        self.url_entry = ttk.Entry(url_frame, textvariable=self.url_var, font=("Arial", 10))
        self.url_entry.grid(row=0, column=0, sticky=(tk.W, tk.E), padx=(0, 10))
        
        # 清空按钮
        clear_btn = ttk.Button(url_frame, text="清空", command=self.clear_url)
        clear_btn.grid(row=0, column=1)
        
        # 保存路径区域
        path_frame = ttk.LabelFrame(main_frame, text="保存路径", padding="10")
        path_frame.grid(row=2, column=0, columnspan=3, sticky=(tk.W, tk.E), pady=(0, 10))
        path_frame.columnconfigure(0, weight=1)
        
        self.path_var = tk.StringVar()
        self.path_var.set(os.path.expanduser("~/Downloads"))  # 默认下载路径
        
        self.path_entry = ttk.Entry(path_frame, textvariable=self.path_var, font=("Arial", 10))
        self.path_entry.grid(row=0, column=0, sticky=(tk.W, tk.E), padx=(0, 10))
        
        # 浏览按钮
        browse_btn = ttk.Button(path_frame, text="浏览", command=self.browse_folder)
        browse_btn.grid(row=0, column=1)
        
        # Cookie管理区域
        cookie_frame = ttk.LabelFrame(main_frame, text="Cookie管理", padding="10")
        cookie_frame.grid(row=3, column=0, columnspan=3, sticky=(tk.W, tk.E), pady=(0, 10))
        cookie_frame.columnconfigure(0, weight=1)
        
        # Cookie输入框
        ttk.Label(cookie_frame, text="Cookie:").grid(row=0, column=0, sticky=tk.W, pady=(0, 5))
        self.cookie_var = tk.StringVar()
        self.cookie_entry = tk.Text(cookie_frame, height=3, font=("Consolas", 9), wrap=tk.WORD)
        self.cookie_entry.grid(row=1, column=0, sticky=(tk.W, tk.E), padx=(0, 10))
        
        # Cookie操作按钮
        cookie_btn_frame = ttk.Frame(cookie_frame)
        cookie_btn_frame.grid(row=1, column=1, sticky=(tk.N, tk.S))
        
        self.update_cookie_btn = ttk.Button(cookie_btn_frame, text="更新Cookie", command=self.update_cookie)
        self.update_cookie_btn.grid(row=0, column=0, pady=(0, 5), sticky=(tk.W, tk.E))
        
        self.test_cookie_btn = ttk.Button(cookie_btn_frame, text="测试Cookie", command=self.test_cookie)
        self.test_cookie_btn.grid(row=1, column=0, pady=(0, 5), sticky=(tk.W, tk.E))
        
        self.clear_cookie_btn = ttk.Button(cookie_btn_frame, text="清空", command=self.clear_cookie)
        self.clear_cookie_btn.grid(row=2, column=0, pady=(0, 5), sticky=(tk.W, tk.E))
        
        self.import_cookie_btn = ttk.Button(cookie_btn_frame, text="从剪贴板导入", command=self.import_cookie_from_clipboard)
        self.import_cookie_btn.grid(row=3, column=0, sticky=(tk.W, tk.E))
        
        # Cookie状态显示
        self.cookie_status_label = ttk.Label(cookie_frame, text="Cookie状态: 已加载", foreground="green")
        self.cookie_status_label.grid(row=2, column=0, sticky=tk.W, pady=(5, 0))
        
        # 下载选项区域
        options_frame = ttk.LabelFrame(main_frame, text="下载选项", padding="10")
        options_frame.grid(row=4, column=0, columnspan=3, sticky=(tk.W, tk.E), pady=(0, 10))
        
        # 视频质量选择
        ttk.Label(options_frame, text="视频质量:").grid(row=0, column=0, sticky=tk.W, padx=(0, 10))
        self.quality_var = tk.StringVar(value="1080p")
        self.quality_combo = ttk.Combobox(options_frame, textvariable=self.quality_var, 
                                        values=["4K", "1080p", "720p", "480p", "360p"], state="readonly")
        self.quality_combo.grid(row=0, column=1, sticky=tk.W, padx=(0, 20))
        
        # 存储可用分辨率信息
        self.available_qualities = set()
        
        # 下载音频选项
        self.audio_var = tk.BooleanVar(value=True)
        audio_check = ttk.Checkbutton(options_frame, text="同时下载音频", variable=self.audio_var)
        audio_check.grid(row=0, column=2, sticky=tk.W)
        
        # 控制按钮区域
        button_frame = ttk.Frame(main_frame)
        button_frame.grid(row=5, column=0, columnspan=3, pady=(0, 10))
        
        # 下载按钮
        self.download_btn = ttk.Button(button_frame, text="开始下载", command=self.start_download,
                                     style="Accent.TButton")
        self.download_btn.grid(row=0, column=0, padx=(0, 10))
        
        # 停止按钮
        self.stop_btn = ttk.Button(button_frame, text="停止下载", command=self.stop_download,
                                 state="disabled")
        self.stop_btn.grid(row=0, column=1, padx=(0, 10))
        
        # 进度条区域
        progress_frame = ttk.LabelFrame(main_frame, text="下载进度", padding="10")
        progress_frame.grid(row=6, column=0, columnspan=3, sticky=(tk.W, tk.E), pady=(0, 10))
        progress_frame.columnconfigure(0, weight=1)
        
        # 进度条
        self.progress_var = tk.DoubleVar()
        self.progress_bar = ttk.Progressbar(progress_frame, variable=self.progress_var, 
                                          maximum=100, length=400)
        self.progress_bar.grid(row=0, column=0, sticky=(tk.W, tk.E), pady=(0, 5))
        
        # 进度标签
        self.progress_label = ttk.Label(progress_frame, text="准备就绪")
        self.progress_label.grid(row=1, column=0, sticky=tk.W)
        
        # 状态显示区域
        status_frame = ttk.LabelFrame(main_frame, text="状态信息", padding="10")
        status_frame.grid(row=7, column=0, columnspan=3, sticky=(tk.W, tk.E, tk.N, tk.S), pady=(0, 10))
        status_frame.columnconfigure(0, weight=1)
        status_frame.rowconfigure(0, weight=1)
        main_frame.rowconfigure(7, weight=1)
        
        # 状态文本框
        self.status_text = scrolledtext.ScrolledText(status_frame, height=8, font=("Consolas", 9))
        self.status_text.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
        
        # 底部信息
        info_frame = ttk.Frame(main_frame)
        info_frame.grid(row=8, column=0, columnspan=3, sticky=(tk.W, tk.E))
        
        self.status_label = ttk.Label(info_frame, text="就绪", foreground="green")
        self.status_label.grid(row=0, column=0, sticky=tk.W)
        
        # 版本信息
        version_label = ttk.Label(info_frame, text="v1.0.0", foreground="gray")
        version_label.grid(row=0, column=1, sticky=tk.E)
        
    def clear_url(self):
        """清空URL输入框"""
        self.url_var.set("")
        
    def browse_folder(self):
        """浏览选择保存文件夹"""
        folder = filedialog.askdirectory(initialdir=self.path_var.get())
        if folder:
            self.path_var.set(folder)
            
    def validate_url(self, url):
        """验证B站URL格式"""
        if not url:
            return False, "请输入视频链接"
        
        # 检查是否是B站链接
        if "bilibili.com" not in url and "b23.tv" not in url:
            return False, "请输入有效的B站视频链接"
            
        return True, "URL格式正确"
    
    def get_video_urls_by_quality(self, datas, quality):
        """根据选择的质量获取对应的视频和音频URL"""
        try:
            # 检查该分辨率是否可用
            if quality not in self.available_qualities:
                raise Exception(f"该视频不支持{quality}分辨率，请选择其他分辨率")
            
            # 分辨率映射
            quality_mapping = {
                "4K": {"width": 1920, "height": 1080, "priority": [120, 80]},
                "1080p": {"width": 1920, "height": 1080, "priority": [80, 112]},
                "720p": {"width": 1280, "height": 720, "priority": [64]},
                "480p": {"width": 852, "height": 480, "priority": [32]},
                "360p": {"width": 640, "height": 360, "priority": [16]}
            }
            
            if quality not in quality_mapping:
                raise Exception(f"不支持的质量选项: {quality}")
            
            target_resolution = quality_mapping[quality]
            video_url = None
            audio_url = None
            
            # 查找匹配的视频URL
            for video in datas['data']['dash']['video']:
                if (video['width'] == target_resolution['width'] and 
                    video['height'] == target_resolution['height'] and
                    video['id'] in target_resolution['priority']):
                    video_url = video['baseUrl']
                    self.log_message(f"找到{quality}视频: {video['width']}x{video['height']} (id:{video['id']})")
                    break
            
            if not video_url:
                # 如果没找到精确匹配，尝试按优先级查找
                for priority_id in target_resolution['priority']:
                    for video in datas['data']['dash']['video']:
                        if video['id'] == priority_id:
                            video_url = video['baseUrl']
                            self.log_message(f"找到{quality}视频 (按ID匹配): {video['width']}x{video['height']} (id:{video['id']})")
                            break
                    if video_url:
                        break
            
            if not video_url:
                raise Exception(f"未找到{quality}质量的视频")
            
            # 获取音频URL (选择最高质量的音频)
            if datas['data']['dash']['audio']:
                # 按带宽排序，选择最高质量的音频
                audio_list = sorted(datas['data']['dash']['audio'], 
                                  key=lambda x: x.get('bandwidth', 0), reverse=True)
                audio_url = audio_list[0]['baseUrl']
                self.log_message(f"找到音频: {audio_list[0]['mimeType']} (带宽:{audio_list[0]['bandwidth']})")
            
            return video_url, audio_url
            
        except Exception as e:
            raise Exception(f"解析视频质量失败: {str(e)}")
    
    def parse_video_data(self, html_content):
        """解析视频数据，支持多种格式"""
        try:
            # 方法1: 尝试从script标签中提取
            data_match = re.findall("window.__playinfo__=(.*?)</script>", html_content)
            if data_match:
                return json.loads(data_match[0])
            
            # 方法2: 尝试直接提取JSON对象
            json_start = html_content.find('{')
            if json_start != -1:
                # 找到JSON对象的结束位置
                brace_count = 0
                json_end = json_start
                for i, char in enumerate(html_content[json_start:], json_start):
                    if char == '{':
                        brace_count += 1
                    elif char == '}':
                        brace_count -= 1
                        if brace_count == 0:
                            json_end = i + 1
                            break
                
                json_content = html_content[json_start:json_end]
                return json.loads(json_content)
            
            raise Exception("无法找到视频数据")
            
        except json.JSONDecodeError as e:
            raise Exception(f"JSON解析失败: {str(e)}")
        except Exception as e:
            raise Exception(f"解析视频数据失败: {str(e)}")
    
    def update_available_qualities(self, datas):
        """检测并记录可用的质量选项"""
        try:
            if 'data' not in datas or 'dash' not in datas['data']:
                return
            
            self.available_qualities.clear()
            
            # 分析可用的分辨率
            for video in datas['data']['dash']['video']:
                width = video.get('width', 0)
                height = video.get('height', 0)
                video_id = video.get('id', 0)
                
                if width == 1920 and height == 1080:
                    if video_id in [120, 112]:
                        self.available_qualities.add("4K")
                    elif video_id == 80:
                        self.available_qualities.add("1080p")
                elif width == 1280 and height == 720:
                    self.available_qualities.add("720p")
                elif width == 852 and height == 480:
                    self.available_qualities.add("480p")
                elif width == 640 and height == 360:
                    self.available_qualities.add("360p")
            
            if self.available_qualities:
                available_list = ', '.join(sorted(self.available_qualities))
                self.log_message(f"✓ 检测到可用分辨率: {available_list}")
                self.log_message("💡 提示: 如果选择的分辨率不可用，程序会弹出提示框")
            else:
                self.log_message("⚠️ 未检测到任何可用分辨率")
            
        except Exception as e:
            self.log_message(f"检测可用分辨率失败: {str(e)}")
        
    def init_cookie(self):
        """初始化Cookie显示"""
        try:
            # 尝试从文件加载Cookie
            if self.load_cookie():
                self.log_message("从文件加载Cookie成功")
            else:
                # 使用默认Cookie
                current_cookie = self.headers.get('cookie', '')
                self.cookie_entry.insert(tk.END, current_cookie)
                self.log_message("使用默认Cookie")
            
            # 设置Cookie状态
            self.cookie_status_label.config(text="Cookie状态: 已加载", foreground="green")
            
        except Exception as e:
            self.log_message(f"初始化Cookie失败: {str(e)}")
    
    def save_cookie(self):
        """保存Cookie到文件"""
        try:
            cookie_data = {
                'cookie': self.headers.get('cookie', ''),
                'timestamp': os.path.getmtime(__file__) if os.path.exists(__file__) else 0
            }
            
            with open(self.cookie_file, 'wb') as f:
                pickle.dump(cookie_data, f)
            
            self.log_message("Cookie已保存到文件")
            return True
            
        except Exception as e:
            self.log_message(f"保存Cookie失败: {str(e)}")
            return False
    
    def load_cookie(self):
        """从文件加载Cookie"""
        try:
            if not os.path.exists(self.cookie_file):
                return False
            
            with open(self.cookie_file, 'rb') as f:
                cookie_data = pickle.load(f)
            
            if 'cookie' in cookie_data and cookie_data['cookie']:
                # 更新headers中的cookie
                self.headers['cookie'] = cookie_data['cookie']
                # 在输入框中显示
                self.cookie_entry.insert(tk.END, cookie_data['cookie'])
                return True
            
            return False
            
        except Exception as e:
            self.log_message(f"加载Cookie失败: {str(e)}")
            return False
    
    def update_cookie(self):
        """更新Cookie"""
        try:
            new_cookie = self.cookie_entry.get("1.0", tk.END).strip()
            
            if not new_cookie:
                messagebox.showwarning("警告", "请输入Cookie")
                return
            
            # 更新headers中的cookie
            self.headers['cookie'] = new_cookie
            
            # 保存Cookie到文件
            if self.save_cookie():
                self.log_message("Cookie已保存到文件")
            
            # 更新状态
            self.cookie_status_label.config(text="Cookie状态: 已更新", foreground="blue")
            self.log_message("Cookie已更新")
            
            # 显示成功消息
            messagebox.showinfo("成功", "Cookie更新成功！")
            
        except Exception as e:
            self.log_message(f"更新Cookie失败: {str(e)}")
            messagebox.showerror("错误", f"更新Cookie失败: {str(e)}")
    
    def test_cookie(self):
        """测试Cookie是否有效"""
        try:
            self.log_message("正在测试Cookie有效性...")
            
            # 使用一个简单的B站API来测试Cookie
            test_url = "https://api.bilibili.com/x/web-interface/nav"
            response = requests.get(test_url, headers=self.headers, timeout=10)
            
            if response.status_code == 200:
                data = response.json()
                if data.get('code') == 0:
                    # Cookie有效
                    self.cookie_status_label.config(text="Cookie状态: 有效", foreground="green")
                    self.log_message("✓ Cookie测试成功，可以正常使用")
                    messagebox.showinfo("测试结果", "Cookie有效，可以正常下载视频！")
                else:
                    # Cookie可能无效
                    self.cookie_status_label.config(text="Cookie状态: 可能无效", foreground="orange")
                    self.log_message("⚠ Cookie可能无效，建议更新")
                    messagebox.showwarning("测试结果", "Cookie可能无效，建议更新Cookie")
            else:
                # 请求失败
                self.cookie_status_label.config(text="Cookie状态: 无效", foreground="red")
                self.log_message("✗ Cookie测试失败")
                messagebox.showerror("测试结果", "Cookie无效，请更新Cookie")
                
        except Exception as e:
            self.log_message(f"测试Cookie失败: {str(e)}")
            self.cookie_status_label.config(text="Cookie状态: 测试失败", foreground="red")
            messagebox.showerror("错误", f"测试Cookie失败: {str(e)}")
    
    def clear_cookie(self):
        """清空Cookie输入框"""
        self.cookie_entry.delete("1.0", tk.END)
        self.cookie_status_label.config(text="Cookie状态: 已清空", foreground="gray")
        self.log_message("Cookie已清空")
    
    def import_cookie_from_clipboard(self):
        """从剪贴板导入Cookie"""
        try:
            # 获取剪贴板内容
            clipboard_content = self.root.clipboard_get()
            
            if not clipboard_content.strip():
                messagebox.showwarning("警告", "剪贴板为空")
                return
            
            # 清空当前内容并插入剪贴板内容
            self.cookie_entry.delete("1.0", tk.END)
            self.cookie_entry.insert("1.0", clipboard_content)
            
            # 自动更新Cookie
            self.update_cookie()
            
            self.log_message("已从剪贴板导入Cookie")
            
        except tk.TclError:
            messagebox.showwarning("警告", "无法访问剪贴板")
        except Exception as e:
            self.log_message(f"从剪贴板导入Cookie失败: {str(e)}")
            messagebox.showerror("错误", f"从剪贴板导入Cookie失败: {str(e)}")
    
    def log_message(self, message):
        """在状态区域添加日志消息"""
        self.status_text.insert(tk.END, f"{message}\n")
        self.status_text.see(tk.END)
        self.root.update_idletasks()
        
    def update_progress(self, progress, message=""):
        """更新下载进度"""
        self.progress_var.set(progress)
        if message:
            self.progress_label.config(text=message)
        self.root.update_idletasks()
        
    def start_download(self):
        """开始下载"""
        url = self.url_var.get().strip()
        
        # 验证输入
        is_valid, error_msg = self.validate_url(url)
        if not is_valid:
            messagebox.showerror("错误", error_msg)
            return
            
        # 检查保存路径
        save_path = self.path_var.get().strip()
        if not os.path.exists(save_path):
            try:
                os.makedirs(save_path)
            except Exception as e:
                messagebox.showerror("错误", f"无法创建保存目录: {e}")
                return
        
        # 开始下载
        self.downloading = True
        self.download_btn.config(state="disabled")
        self.stop_btn.config(state="normal")
        self.status_label.config(text="下载中...", foreground="blue")
        
        # 清空状态文本
        self.status_text.delete(1.0, tk.END)
        
        # 在新线程中执行下载
        self.download_thread = threading.Thread(target=self.download_video, args=(url, save_path))
        self.download_thread.daemon = True
        self.download_thread.start()
        
    def stop_download(self):
        """停止下载"""
        self.downloading = False
        self.download_btn.config(state="normal")
        self.stop_btn.config(state="disabled")
        self.status_label.config(text="已停止", foreground="red")
        self.log_message("下载已停止")
        
    def download_video(self, url, save_path):
        """下载视频的主函数"""
        try:
            self.log_message(f"开始下载: {url}")
            self.log_message(f"保存路径: {save_path}")
            self.log_message(f"视频质量: {self.quality_var.get()}")
            self.log_message(f"下载音频: {'是' if self.audio_var.get() else '否'}")
            
            # 步骤1: 获取视频信息
            self.update_progress(5, "正在获取视频信息...")
            self.log_message("正在获取视频信息...")
            
            response = requests.get(url, headers=self.headers)
            if response.status_code != 200:
                raise Exception(f"无法访问视频页面，状态码: {response.status_code}")
            
            # 解析视频数据
            datas = self.parse_video_data(response.text)
            
            # 检查是否有dash格式
            if 'data' not in datas or 'dash' not in datas['data']:
                raise Exception("该视频不支持下载，可能是版权限制或需要登录")
            
            # 更新可用的质量选项
            self.update_available_qualities(datas)
            
            # 根据选择的质量获取视频和音频URL
            selected_quality = self.quality_var.get()
            
            # 检查选择的分辨率是否可用
            if selected_quality not in self.available_qualities:
                available_list = ', '.join(sorted(self.available_qualities)) if self.available_qualities else "无"
                messagebox.showwarning(
                    "分辨率不可用", 
                    f"该视频不支持{selected_quality}分辨率！\n\n"
                    f"可用分辨率: {available_list}\n\n"
                    f"请重新选择分辨率后再次下载。"
                )
                self.downloading = False
                self.download_btn.config(state="normal")
                self.stop_btn.config(state="disabled")
                self.status_label.config(text="下载已取消", foreground="orange")
                return
            
            video_url, audio_url = self.get_video_urls_by_quality(datas, selected_quality)
            
            self.log_message(f"视频信息获取成功，选择质量: {selected_quality}")
            
            # 步骤2: 创建临时目录
            self.update_progress(10, "准备下载...")
            temp_dir = os.path.join(save_path, "temp_download")
            if not os.path.exists(temp_dir):
                os.makedirs(temp_dir)
            
            video_temp_path = os.path.join(temp_dir, "video_temp.mp4")
            audio_temp_path = os.path.join(temp_dir, "audio_temp.m4a")
            
            # 步骤3: 下载视频文件
            if not self.downloading:
                return
                
            self.update_progress(20, "正在下载视频文件...")
            self.log_message("开始下载视频文件...")
            
            video_response = requests.get(video_url, headers=self.headers, stream=True)
            video_response.raise_for_status()
            
            total_size = int(video_response.headers.get('content-length', 0))
            downloaded_size = 0
            
            with open(video_temp_path, 'wb') as f:
                for chunk in video_response.iter_content(chunk_size=8192):
                    if not self.downloading:
                        self.log_message("下载被用户停止")
                        return
                    
                    f.write(chunk)
                    downloaded_size += len(chunk)
                    
                    if total_size > 0:
                        progress = 20 + (downloaded_size / total_size) * 40  # 20-60%
                        self.update_progress(progress, f"下载视频: {downloaded_size//1024//1024}MB")
            
            self.log_message("视频文件下载完成")
            
            # 步骤4: 下载音频文件
            if not self.downloading:
                return
                
            if self.audio_var.get():
                self.update_progress(60, "正在下载音频文件...")
                self.log_message("开始下载音频文件...")
                
                audio_response = requests.get(audio_url, headers=self.headers, stream=True)
                audio_response.raise_for_status()
                
                with open(audio_temp_path, 'wb') as f:
                    for chunk in audio_response.iter_content(chunk_size=8192):
                        if not self.downloading:
                            self.log_message("下载被用户停止")
                            return
                        f.write(chunk)
                
                self.log_message("音频文件下载完成")
            
            # 步骤5: 合成视频
            if not self.downloading:
                return
                
            self.update_progress(80, "正在合成视频...")
            self.log_message("开始合成视频...")
            
            # 生成输出文件名
            import time
            timestamp = int(time.time())
            output_filename = f"bilibili_video_{timestamp}.mp4"
            output_path = os.path.join(save_path, output_filename)
            
            if self.audio_var.get():
                # 使用ffmpeg合成视频和音频
                self.merge_video_audio(video_temp_path, audio_temp_path, output_path)
            else:
                # 只保存视频文件
                import shutil
                shutil.move(video_temp_path, output_path)
            
            # 步骤6: 清理临时文件
            self.update_progress(95, "清理临时文件...")
            self.cleanup_temp_files(temp_dir)
            
            # 完成
            self.update_progress(100, "下载完成！")
            self.log_message(f"视频已保存到: {output_path}")
            
            self.downloading = False
            self.download_btn.config(state="normal")
            self.stop_btn.config(state="disabled")
            self.status_label.config(text="下载完成", foreground="green")
            
            # 显示完成消息
            messagebox.showinfo("完成", f"视频下载完成！\n保存位置: {output_path}")
            
        except Exception as e:
            self.log_message(f"下载出错: {str(e)}")
            self.downloading = False
            self.download_btn.config(state="normal")
            self.stop_btn.config(state="disabled")
            self.status_label.config(text="下载失败", foreground="red")
            messagebox.showerror("错误", f"下载失败: {str(e)}")
    
    def merge_video_audio(self, video_path, audio_path, output_path):
        """使用ffmpeg合并视频和音频"""
        try:
            process = subprocess.Popen(
                [
                    'ffmpeg',
                    '-y',
                    '-i', video_path,
                    '-i', audio_path,
                    '-c:v', 'copy',
                    '-c:a', 'copy',
                    '-strict', 'experimental',
                    '-shortest',
                    output_path
                ],
                stdout=subprocess.PIPE,
                stderr=subprocess.PIPE,
                text=True,
                encoding='utf-8',
                errors='ignore'
            )
            
            stdout, stderr = process.communicate()
            
            if process.returncode != 0:
                raise Exception(f"视频合成失败: {stderr}")
            
            self.log_message("视频合成成功")
            
        except FileNotFoundError:
            raise Exception("未找到ffmpeg，请确保已安装ffmpeg并添加到系统PATH")
        except Exception as e:
            raise Exception(f"视频合成失败: {str(e)}")
    
    def cleanup_temp_files(self, temp_dir):
        """清理临时文件"""
        try:
            import shutil
            if os.path.exists(temp_dir):
                shutil.rmtree(temp_dir)
            self.log_message("临时文件清理完成")
        except Exception as e:
            self.log_message(f"清理临时文件时出错: {str(e)}")

def main():
    root = tk.Tk()
    
    # 设置样式
    style = ttk.Style()
    style.theme_use('clam')
    
    # 创建应用
    app = BilibiliDownloader(root)
    
    # 运行应用
    root.mainloop()

if __name__ == "__main__":
    main()
